# 基于高德搭建选址组件

先来看看效果

主要有几个功能:

  1. 打开时定位
  2. 如果有传入地址则跳转至该地址
  3. 选省市区,然后地图跳到选择区域并画出来
  4. 启动拖动选址功能
  5. 根据选址自动变换省市区及详细地址

# 初始化高德

# 挂载高德js

这里选择的是回调的方法,密钥自行去官网获取

async beforeCreate () {
  window.onLoad = () => {
    this.init()
  }
  const url = `https://webapi.amap.com/maps?v=2.0&key=${AMapKey}&callback=onLoad`
  const jsapi = document.createElement('script')
  jsapi.charset = 'utf-8'
  jsapi.src = url
  document.head.appendChild(jsapi)
},

在主js加载完成后加载样式的js,并初始化地图实例

async init () {
  await this.utils.getScript(`//webapi.amap.com/ui/1.1/main.js`)
  this.initAMap()
},

# 初始化主逻辑

从父组件传入的信息中获取经纬度、城市等信息

  • 如果没经纬度则走选地址
    1. 先初始化一个地图实例
    2. 如果没有需要移动去的城市id,则调用定位,显示定位动画
    3. 定位完成后纪录经纬度,并根据经纬度查找中文地址以及找出公司自己的城市id
  • 有经纬度则初始化时传入,并渲染一个标记出来,下文有帖代码
  • 然后初始化一些插件
initAMap () {
  this.latitude = this.mapConfig.latitude
  this.longitude = this.mapConfig.longitude
  this.searchText = this.mapConfig.city
  if (window.AMap) {
    const AMap = window.AMap
    if (!this.mapConfig.latitude || !this.mapConfig.longitude) {
      // 没经纬度表示是选地址
      this.map = new AMap.Map('map', {
        resizeEnable: true,
        zoom: 14,
        viewMode: '3D'
      })
      if (!this.mapConfig.moveToAreaId) {
        this.showLoading = true
        AMap.plugin('AMap.Geolocation', () => {
          let geolocation = new AMap.Geolocation({
            enableHighAccuracy: true, // 是否使用高精度定位,默认:true
            timeout: 10000,          // 超过10秒后停止定位,默认:无穷大
            maximumAge: 0,           // 定位结果缓存0毫秒,默认:0
            convert: true,           // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true
            showButton: true,        // 显示定位按钮,默认:true
            buttonPosition: 'LB',    // 定位按钮停靠位置,默认:'LB',左下角
            buttonOffset: new AMap.Pixel(100, 200), // 定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
            showMarker: true,        // 定位成功后在定位到的位置显示点标记,默认:true
            showCircle: true,        // 定位成功后用圆圈表示定位精度范围,默认:true
            panToLocation: true,     // 定位成功后将定位到的位置作为地图中心点,默认:true
            zoomToAccuracy: true      // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
          })
          this.map.addControl(geolocation)
          geolocation.getCurrentPosition((status, result) => {
            if (status === 'complete') {
              this.onComplete(result)
            } else {
              this.onError(result)
            }
            this.showLoading = false
          })
        })
      }
    } else {
      // 有经纬度表示是显示该地址
      this.map = new AMap.Map('map', {
        center: [this.mapConfig.longitude, this.mapConfig.latitude],
        resizeEnable: true,
        zoom: 14,
        viewMode: '3D'
      })
      this.$nextTick(() => {
        this.renderMarker([this.mapConfig.longitude, this.mapConfig.latitude])
      })
    }
    this.setMapPlugin(AMap)
    // 选地址时才绑定poi搜索
    if (this.mapConfig.showSearchBox) {
      this.bindDragMapFunc(!this.mapConfig.moveToAreaId)
      this.bindEvent()
      // this.bindClick(this.map)
    }
    if (this.mapConfig.moveToAreaId) {
      this.$emit('areaChange', this.findCityNameByAreaId(this.mapConfig.areaId), this.mapConfig.areaId, '', true)
    }
  }
},

# 生成marker标记

/**
 * 生成marker标记
 * @param lngLat
 */
renderMarker (lngLat) {
  const AMap = window.AMap
  /* eslint-disable */
  if (AMap && this.map) {
    this.positionPicker && this.positionPicker.stop()
    this.marker && this.map.remove(this.marker)
    // const icon = new AMap.Icon({
    //   size: new AMap.Size(30, 40),    // 图标尺寸
    //   image: '//image.greenplayer.cn/share/img/icon/icon_court.svg',  // Icon的图像
    //   // imageOffset: new AMap.Pixel(0, -60),  // 图像相对展示区域的偏移量,适于雪碧图等
    //   imageSize: new AMap.Size(30, 40)   // 根据所设置的大小拉伸或压缩图片
    // });
    this.marker = new AMap.Marker({
      map: this.map,
      position: lngLat
      // icon: icon
    })
    // this.map.setFitView()
    this.map.setCenter(lngLat)
    this.map.on('touchstart', () => {
      this.positionPicker && !this.positionPicker.started && this.positionPicker.start()
      this.map.clearEvents('touchstart')
    })
  }
},

# 初始化插件

setMapPlugin (AMap) {
  let plugins = ['AMap.ToolBar', 'AMap.Geolocation', 'AMap.ControlBar', 'AMap.DistrictSearch']
  AMap.plugin(plugins, () => {
    this.map.addControl(new AMap.ToolBar())
    this.map.addControl(new AMap.Geolocation({
      position: {
        left: '0.4rem',
        bottom: '0.4rem'
      }
    }))
    this.map.addControl(new AMap.ControlBar({
      position: {
        right: '0.3rem',
        top: '1.2rem'
      },
      showControlButton: true  // 是否显示倾斜、旋转按钮。默认为 true
    }))
  })
},

# 启用拖动选点

/**
 * 拖动选点
 */
bindDragMapFunc (setInfo = true) {
  this.poiPicker = null
  const AMapUI = window.AMapUI
  // 加载PositionPicker,loadUI的路径参数为模块名中 'ui/' 之后的部分
  AMapUI.loadUI(['misc/PositionPicker'], (PositionPicker) => {
    this.positionPicker = new PositionPicker({
      mode: 'dragMap', // 设定为拖拽地图模式,可选'dragMap'、'dragMarker',默认为'dragMap'
      map: this.map // 依赖地图对象
    })
    this.positionPicker.on('success', (positionResult) => {
      console.log('positionResult', positionResult)
      if (setInfo) {
        this.getPosition(positionResult.position)
        this.lngGetNames(positionResult.regeocode, true)
      }
      // this.renderMarker([positionResult.position.lng, positionResult.position.lat])
    })
    this.positionPicker.on('fail', (positionResult) => {
      // 海上或海外无法获得地址信息
    })
    this.positionPicker.start()
    if (setInfo) {
    }
  })
},

# 监听事件

主要用在搜索后,拖动地图或点击等事件中隐藏搜索列表

/**
 * 地图事件,如果有显示搜索列表,则隐藏
 */
bindEvent () {
  const eventList = ['click', 'mapmove', 'mousewheel']
  eventList.forEach(item => {
    this.map.on(item, () => {
      if (this.showSearchList) this.showSearchList = false
    })
  })
  this.$on('hook:beforeDestroy', () => {
    eventList.forEach(item => {
      this.map.clearEvents(item)
    })
  })
},

# 画出所选行政区

  1. 没有初始化时先初始化
  2. 根据自家城市选择器传来的地区,获取区域级别(省市区),并给行政区搜索插件设置上
  3. 搜索传入的最小的行政区,有结果后清除上一次画的区域
  4. 这里注意返回结果可能存在多个,于是根据选的区域找真实的那个
  5. 画出区域
/**
 * 选择城市后打标记并移过去
 */
mapToChooseCity () {
  this.positionPicker && this.positionPicker.stop()
  const AMap = window.AMap
  if (!this.district) {
    var opts = {
      subdistrict: 0,   // 获取边界不需要返回下级行政区
      extensions: 'all',  // 返回行政区边界坐标组等具体信息
      level: 'district'  // 查询行政级别为 市
    }
    this.district = new AMap.DistrictSearch(opts)
  }
  const cityList = this.mapConfig.areaName.split(' ').filter(item => item)
  const addressLevel = ['province', 'city', 'district']
  const address = cityList[cityList.length - 1]
  // 行政区查询
  this.district.setLevel(addressLevel[cityList.length - 1])
  this.district.search(address, (status, result) => {
    this.map.remove(this.polygons)// 清除上次结果
    this.polygons = []
    const list = result.districtList
    if (list) {
      let findItemIndex = 0
      if (list.length > 0) {
        // 全国名字有多个时,查具体地址,然后比对省,找出对的
        let promiseList = []
        list.forEach(item => {
          promiseList.push(this.findItemDetailAddress(item))
        })
        Promise.all(promiseList).then(x => {
          const realCityIndex = x.findIndex(city => {
            return city.regeocode.addressComponent.province.includes(cityList[0])
          })
          if (realCityIndex !== -1) {
            findItemIndex = realCityIndex
          }
          this.drawCityArea(result.districtList[findItemIndex])
        })
      } else {
        this.drawCityArea(result.districtList[findItemIndex])
      }
    }
  })
},

画区域很简单,官网直接有例子的

地图移动有个过程,于是延迟再执行拖动选点

drawCityArea (findItem) {
  const AMap = window.AMap
  var bounds = findItem.boundaries
  if (bounds) {
    for (var i = 0, l = bounds.length; i < l; i++) {
      // 生成行政区划polygon
      this.polygon = new AMap.Polygon({
        strokeWeight: 1,
        path: bounds[i],
        fillOpacity: 0.4,
        fillColor: '#80d8ff',
        strokeColor: '#0091ea'
      })
      this.polygons.push(this.polygon)
    }
  }
  this.map.add(this.polygons)
  this.map.setFitView(this.polygons)// 视口自适应
  setTimeout(() => {
    this.positionPicker.start()
  }, 1500)
},